/* Original Author:  Dominick O'Dierno
 * Date: 2007   odiernod@gmail.com     --Originally intended for use with DOMercury:  http://www.odierno.com/domercury
 * Description: Manages indexing, re-indexing, storage, sorting and retrieval of DOMercury index Items
 * 
 * --CHANGELOG--
 * 
 * 2008-04-15 Tao Klerks: 
 *  - Consolidated largely duplicated "GetItem" code into single method
 *  - Added result-set caching on main index for faster search response times, esp. on low delay settings
 * 
 * 2010=1-31 Dominick O'Dierno
 *  - Added Subindex and search for Paramters, ParamsList comes back now as index of TextItems
 *  - Moved SystemWindows and file paths to the top of the main index search
 * */

using System;
using System.Windows.Forms;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using System.Xml;
using ManagedWinapi.Windows;
using DOMercury.Items;
using DOMercury.Interfaces;


namespace DOMercury.Managers            
{
    /// <summary>
    /// Manages all functionality of the DOMercury Index
    /// </summary>
    static class IndexManager
    {
        //File extensions not to be indexed
        public static List<string> FileExtensionsToIgnore;
        //Flag telling DOMercury to halt refreshing the index
        public static bool PauseRefresh = false;
        //Flag whether to use smartsearch or quicksearch
        private static bool SMARTSEARCH = ConfigurationManager.SmartSearch;
        //The maximum number of results to return on a GetItems request
        private static int MATCHLIM = ConfigurationManager.MaxResults;
        //Flag for sub indexes to either return everything or just results that match
        private static bool SearchOn;
        //Flag for param sub indexes to either return everything or just results that match
        private static bool pSearchOn;
        //the stack for managing sub indexes
        private static IndexStack istack;
        //The stack for managing parent items
        private static ItemStack itstack;
        //the stack for managing keywords per sub index jump
        private static StringStack sstack;
        //the stack for managing param sub indexes
        private static IndexStack pistack;
        //The stack for managing param parent items
        private static ItemStack pitstack;
        //the stack for managing param keywords per sub index jump
        private static StringStack psstack;
        //The list of previously used text items
        private static List<TextItem> history;
        //The list of start menu indexed items
        private static List<StartMenuItem> smlist;
        //The main index
        private static List<IItemContainer> index;
        //the current index
        private static List<IItemContainer> currentIndex;
        //The main param index
        private static List<IItemContainer> pindex;
        //the current param index
        private static List<IItemContainer> pcurrentIndex;
        //textbox autocompletestringcollection of used Params texts
        private static AutoCompleteStringCollection Params;
        //List of TextItems matching the Params AutocompleteStringCollection
        private static List<IItem> ParamsTexts;
        //index searchable list of ParamsTexts
        private static List<IItemContainer> ParamsTextsIndex;
        //The hash of item priorities
        private static System.Collections.Hashtable PriorityHash;

        //the collection of results - for each (partial) search string the search results are stored
        private static Dictionary<string, List<IItemContainer>> cachedIndexResults;

        //the collection of results - for each (partial) search string the progress through the index list is stored
        private static Dictionary<string, int> cachedIndexResultIndices;

        //just a flag, to know when interim results should be discarded
        private static bool cachedIndexesUseSmartSearch;

        public static List<IItem> ParamTextHistory
        {
            get
            {
                return ParamsTexts;
            }
        }


        /// <summary>
        /// Returns true if the current index is the main index
        /// </summary>
        public static bool IsMainIndex
        {
            get
            {
                return (currentIndex == index);
            }
        }
        
        /// <summary>
        /// Gets or Sets if current search mode is smart search
        /// </summary>
        public static bool SmartSearch
        {
            get
            {
                return SMARTSEARCH;
            }
            set
            {
                SMARTSEARCH = value;
            }
        }

        /// <summary>
        /// Gets or Sets the maximum number of results to return
        /// </summary>
        public static int MaximumResults
        {
            get
            {
                return MATCHLIM;
            }
            set
            {
                MATCHLIM = value;
            }
        }

        /// <summary>
        /// Initializes the IndexManager
        /// </summary>
        public static void Load()
        {
            FileExtensionsToIgnore = GetIgnoredFileExtensions();

            PriorityHash = new System.Collections.Hashtable();
            LoadPriorities();
            SearchOn = true;
            history = new List<TextItem>();
            Params = new AutoCompleteStringCollection();
            ParamsTexts = new List<IItem>();
            ParamsTextsIndex = new List<IItemContainer>();
            try
            {
                ReadItems(ref history);
            }
            catch (FileNotFoundException ex)
            {
                MessageBox.Show("Could Not find \"AutoComplete.xml\". Unable to load text items history.", "Error, Unable to find AutoComplete.xml", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            catch (Exception ex)
            {
                MessageBox.Show("\"AutoComplete.xml\" is corrupt or damaged, or possibly in an incorrect format. Unable to load items history.", "Error, Unable to read AutoComplete.xml", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            history.Sort();
            index = new List<IItemContainer>();
            pindex = new List<IItemContainer>();
            istack = new IndexStack(index);
            itstack = new ItemStack();
            sstack = new StringStack();
            pistack = new IndexStack(index);
            pitstack = new ItemStack();
            psstack = new StringStack(); 
            BuildIndex(false);
            currentIndex = index;
            pcurrentIndex = pindex;
            cachedIndexResults = new Dictionary<string, List<IItemContainer>>();
            cachedIndexResultIndices = new Dictionary<string, int>();
            cachedIndexesUseSmartSearch = false;
        }

        /// <summary>
        /// Adds RealTime Items into the index
        /// </summary>
        public static void ReEvaluateRealTimePlugins()
        {
            foreach (IItemRealtimeProvider ipr in PluginManager.RealTimeItemProviders)
            {
                try
                {
                    List<IItem> rtitems = ipr.Items;
                    if (rtitems != null)
                    {
                        foreach (IItem i in rtitems)
                        {
                            index.Add(NewIItemContainerwPriority(i, true, true));
                        }
                    }
                }
                catch { }
            }
            SortIndex();
        }

        private static List<string> GetIgnoredFileExtensions()
        {
            List<string> exts = new List<string>();
            try
            {
                FileStream fs = new FileStream("FileExtensionsToIgnore.txt", FileMode.Open, FileAccess.Read);
                StreamReader sr = new StreamReader(fs);
                while (!sr.EndOfStream)
                {
                    exts.Add(sr.ReadLine().Trim());
                }
            }
            catch {
                //If a previously saved list of file extensions is not found, generate a list here
                exts.Add(".cache");
                exts.Add(".tmp");
                exts.Add(".resx");
                exts.Add(".ncb");
                exts.Add(".obj");
                exts.Add(".manifest");
                exts.Add(".rc");
                exts.Add(".compiled");
                exts.Add(".dll");
                exts.Add(".resources");
                exts.Add(".bin");
                exts.Add(".ilk");
                exts.Add(".o");
                exts.Add(".bak");
                exts.Add(".license");
            }

            return exts;
        }

        public static void SaveIgnoredFileExtensions()
        {
            try
            {
                FileStream fs = new FileStream("FileExtensionsToIgnore.txt", FileMode.OpenOrCreate, FileAccess.Write);
                StreamWriter sw = new StreamWriter(fs);
                foreach (string s in FileExtensionsToIgnore)
                {
                    sw.WriteLine(s.ToLower());
                }
                sw.Flush();
                sw.Close();
                fs.Close();
            }
            catch {}
        }

        /// <summary>
        /// Determines whether a file should be added to the index based on its filename
        /// </summary>
        /// <param name="file">the filepath of the file</param>
        /// <returns>true if should be added, false if should be ignored</returns>
        private static bool ShouldAdd(string file)
        {
            try
            {
                if (FileExtensionsToIgnore.Contains(Path.GetExtension(file).ToLower()))
                {
                    return false;
                }
                //ignore temp files
                else if (Path.GetFileName(file).StartsWith("~"))
                {
                    return false;
                }
                else
                {
                    return true;
                }
            }
            catch
            {
                return true;
            }
        }

        #region HashFunctions
        //Push index priorities to the priority hash.  They are saved for re-indexing
        private static void PushPriorities()
        {
            PriorityHash.Clear();
            foreach (IItemContainer it in index)
            {
                //Only save priorities greater than 0
                if (it.Priority > 0)
                {
                    AddToHash(it);
                }
            }
        }

        //Place priorites from the Priority Hash to the main index
        private static void PullPriorities()
        {
            foreach (IItemContainer it in index)
            {
                GetPriority(it);
            }
        }

        //Place priorites from the Priority Hash onto an index passed in as a parameter
        private static void PullPriorities(List<IItemContainer> nindex)
        {
            foreach (IItemContainer it in nindex)
            {
                GetPriority(it);
            }
        }

        //If the item exists in the priority hash, set its priority
        private static void GetPriority(IItemContainer it)
        {
            int key = GetHash(it);
            if (PriorityHash.Contains(key))
            {
                it.Priority = (int)PriorityHash[key];
            }
        }
        
        //Add or set the items priority in the priority hash
        private static void AddToHash(IItemContainer item)
        {
            int key = GetHash(item);
            if (!PriorityHash.Contains(key))
            {
                PriorityHash.Add(key, item.Priority);
            }
            else
            {
                PriorityHash[key] = item.Priority;
            }
        }

        //Create a Hash Code for an item
        private static int GetHash(IItem item)
        {
            StringBuilder sr = new StringBuilder(item.DisplayName);
            sr.Append(item.DisplayDesc);
            sr.Append(item.ItemType);
            return sr.ToString().GetHashCode();
        }
        
        /// <summary>
        /// Save the priority hash to a file to be retrieved later.
        /// </summary>
        public static void SavePriorities()
        {
            PushPriorities();
            try
            {
                FileStream fs = new FileStream("PersistData.dat", FileMode.Create, FileAccess.Write);
                StreamWriter sw = new StreamWriter(fs);
                foreach (int key in PriorityHash.Keys)
                {
                    sw.WriteLine(key.ToString() + "," + PriorityHash[key].ToString());
                }
                sw.Flush();
                sw.Close();
                fs.Close();
            }
            catch
            {
                //priorities couldn't be saved most likely because another thread is in the process of saving them.
            }
        }

        private static void LoadPriorities()
        {
            FileStream fs = new FileStream("PersistData.dat", FileMode.OpenOrCreate, FileAccess.Read, FileShare.ReadWrite);
            StreamReader sr = new StreamReader(fs);
            while (!sr.EndOfStream)
            {
                string[] temp = sr.ReadLine().Split(',');
                int key = Convert.ToInt32(temp[0]);
                int val = Convert.ToInt32(temp[1]);
                if (!PriorityHash.ContainsKey(key))
                {
                    PriorityHash.Add(key,val);
                }

            }
            sr.Close();
            fs.Close();
        }

        #endregion
        #region Index Management Functions

        //Builds the main DOMercury Index
        private static void BuildIndex(bool Rebuild)
        {
            //If this is not the first time the index is built this instance of DOMercury
            if (Rebuild)
            {
                SavePriorities();
                RebuildIndex();
                return;
            }
            //If this is the first time the index is built, it is built in steps.  The start menu and items history are built first,
            //then set as the index.  The plugins and icons are then built in the backround while the user can use the program.
            //Create a temporary index
            List<IItemContainer> nindex = new List<IItemContainer>();

            //Index the Start Menu
            BuildSMList();
            //Add the start menu to the index
            foreach (IItem i in smlist)
            {
                nindex.Add(NewIItemContainerwPriority(i));
            }

            //Add DOMercury Items to the index
            nindex.Add(new IItemContainer(new CurrentWindowItem(), int.MaxValue)); 
            nindex.Add(new IItemContainer(new RefreshIndexItem()));
            nindex.Add(new IItemContainer(new ScreenSaverItem(), int.MaxValue));
            nindex.Add(new IItemContainer(new MyComputerItem(), int.MaxValue));
            nindex.Add(new IItemContainer(new DeskTopItem(), int.MaxValue));
            nindex.Add(new IItemContainer(new OptionsItem(), int.MaxValue));
            nindex.Add(new IItemContainer(new GoogleItem(), int.MaxValue));
            nindex.Add(new IItemContainer(new ExitItem(), int.MaxValue));
            
            //Add the history to the index
            foreach (IItem i in history)
            {
                nindex.Add(NewIItemContainerwPriority(i));
            }
            nindex.Sort();
            //Set the temporary index to the main index
            if (currentIndex == index)
            {
                index = nindex;
                istack.ResetBase(index);
                currentIndex = index;
            }
            else
            {
                index = nindex;
                istack.ResetBase(index);
            }

            //Get the icons for the items, then index the plugins
            BuildIcons(true);
        }

        //Rebuild the entire index.  This function is done inside a separate thread and the index switch is done at the very end so it is invisible to the user
        private static void pRebuildIndex()
        {
            List<IItemContainer> nindex = new List<IItemContainer>();

            BuildSMList();
            foreach (IItem i in smlist)
            {
                nindex.Add(NewIItemContainerwPriority(i));
            }
            nindex.Add(new IItemContainer(new CurrentWindowItem(), int.MaxValue));
            nindex.Add(new IItemContainer(new RefreshIndexItem()));
            nindex.Add(new IItemContainer(new ScreenSaverItem(), int.MaxValue));
            nindex.Add(new IItemContainer(new MyComputerItem(), int.MaxValue));
            nindex.Add(new IItemContainer(new DeskTopItem(), int.MaxValue));
            nindex.Add(new IItemContainer(new OptionsItem(), int.MaxValue));
            nindex.Add(new IItemContainer(new GoogleItem(), int.MaxValue));
            nindex.Add(new IItemContainer(new ExitItem(), int.MaxValue));

            foreach (IItem i in history)
            {
                nindex.Add(NewIItemContainerwPriority(i));
            }

            //add the items from each plugin
            foreach (IItemProvider p in PluginManager.ItemProviders)
            {
                if (p.Items != null)
                {
                    List<IItemContainer> items = new List<IItemContainer>();
                    foreach (IItem i in p.Items)
                    {
                        if (ShouldAdd(i.DisplayDesc))
                        {
                            items.Add(NewIItemContainerwPriority(i));
                        }
                    }
                    nindex.AddRange(items);
                }
            }
            //set the priorities for the items in the new index
            PullPriorities(nindex);
            nindex.Sort();
            //Set the new index as the DOMercury main index
            if (currentIndex == index)
            {
                index = nindex;
                istack.ResetBase(index);
                currentIndex = index;
            }
            else
            {
                index = nindex;
                istack.ResetBase(index);
            }     
            //generate icons for all the new items
            pBuildIcons(); 
        }

        //builds a new thread to add teh plugin items to the DOMercury index
        private static void IndexPlugins()
        {
            System.ComponentModel.BackgroundWorker bw = new System.ComponentModel.BackgroundWorker();
            bw.DoWork += new System.ComponentModel.DoWorkEventHandler(bwPlugins_DoWork);
            bw.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
            bw.RunWorkerAsync();
            
        }

        //Builds the plugin icons after the plugins have been indexed
        static void bw_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
        {
            BuildIcons(false);
        }

        //Indexes teh plugins after the initial icons have been completed
        static void bw_RunWorkerPCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
        {
            IndexPlugins();
        }

        //Indexes the plugins on a new thread
        private static void bwPlugins_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
        {
            pIndexPlugins();
        }

        //Adds the plugin items to the main index.  
        private static void pIndexPlugins()
        {
            foreach (IItemProvider p in PluginManager.ItemProviders)
            {
                if (p.Items != null)
                {
                    List<IItemContainer> items = new List<IItemContainer>();
                    foreach (IItem i in p.Items)
                    {
                        if (ShouldAdd(i.DisplayDesc))
                        {
                            items.Add(NewIItemContainerwPriority(i));
                        }
                    }
                    //index is being changed, so indterim / cached results are now invalid.
                    cachedIndexResults.Clear();
                    cachedIndexResultIndices.Clear(); 

                    index.AddRange(items);
                }
            }
        }

        /// <summary>
        /// Increases the priority of an item by 1
        /// </summary>
        /// <param name="DisplayName"></param>
        /// <param name="DisplayDescription"></param>
        public static void IncreasePriority(string DisplayName, string DisplayDescription)
        {
            //Find the item which priority needs to be increased
            IItemContainer ip = index.Find(delegate (IItemContainer item){return ((item.DisplayDesc == DisplayDescription)&&(item.DisplayName == DisplayName));});
            if (ip != null)
            {
                if (ip.Priority != int.MaxValue)
                {
                    //if greater than 0, increase teh priority by 1
                    if (ip.Priority > 0)
                    {
                        ip.Priority++;
                    }
                    //if less than zero, set the priority to 1
                    else
                    {
                        ip.Priority = 1;
                    }
                }
            }
        }

        /// <summary>
        /// Change the priority of an item to a specific number
        /// </summary>
        /// <param name="DisplayName">The displayname of the item to be changed</param>
        /// <param name="DisplayDescription">the displaydescription of the item to be changed</param>
        /// <param name="Priority">The new priority for the item</param>
        public static void ChangePriority(string DisplayName, string DisplayDescription, int Priority)
        {
            //find the item which priority needs to be changed
            IItemContainer ip = index.Find(delegate(IItemContainer item) { return ((item.DisplayDesc == DisplayDescription) && (item.DisplayName == DisplayName)); });
            if (ip != null)
            {
                ip.Priority = Priority;
            }
        }

        private static IItemContainer NewIItemContainerwPriority(IItem item)
        {
            return NewIItemContainerwPriority(item, false, false);
        }

        public static IItemContainer NewIItemContainerwPriority(IItem item, bool plugin, bool rtime)
        {
            //Return the new IItemContainer around the IItem with a priority which is decreased by 1 for every 4 characters in the DisplayName
            return new IItemContainer(item, -1 * (item.DisplayName.Length / 4));
        }



        public static void SortIndex()
        {
            //whenever we sort, cached results become inaccurate - clear them.
            cachedIndexResults.Clear();
            cachedIndexResultIndices.Clear(); 

            index.Sort();
        }

        //Generate the icons for the items in the index
        private static void BuildIcons(bool plugs)
        {
            System.ComponentModel.BackgroundWorker bw = new System.ComponentModel.BackgroundWorker();
            bw.DoWork += new System.ComponentModel.DoWorkEventHandler(bwIcon_DoWork);
            if (plugs)
            {
                bw.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(bw_RunWorkerPCompleted);
            }
            else
            {
                PullPriorities();
                SortIndex();               
            }
            bw.RunWorkerAsync();
        }

        private static void bwIcon_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
        {
            pBuildIcons();
        }

        private static void pBuildIcons()
        {
            foreach (IItemProvider p in PluginManager.ItemProviders)
            {
                try
                {
                    //Add custom plugin icons
                    foreach (IconPair i in p.CustomIcons)
                    {
                        IconManager.AddCustomIcon(i.Key, i.Icon);
                    }
                }
                catch
                {

                }
            }


            for (int i = 0; i < index.Count; i++ )
            {
                IconManager.AddIcon(index[i].DisplayDesc);
            }
            foreach (Alias a in AliasManager.AliasList)
            {
                try
                {
                    if (File.Exists(a.Path))
                    {
                        IconManager.AddCustomIcon(a.DisplayDesc, System.Drawing.Icon.ExtractAssociatedIcon(a.Path).ToBitmap());
                    }
                }
                catch
                {
                }
            }
        }

        //Index .lnk and .url files from the Start Menu and the start menu of the specific user
        private static void BuildSMList()
        {
            
            List<StartMenuItem> tsmlist = new List<StartMenuItem>();
            DirectoryInfo di;
            //if Vista
            if (System.Environment.OSVersion.Version.Major == 6)
            {
                di = new DirectoryInfo(System.Environment.GetEnvironmentVariable("ALLUSERSPROFILE") + @"\Microsoft\Windows\Start Menu");
            }
            //else if xp
            else
            {
                di = new DirectoryInfo(System.Environment.GetEnvironmentVariable("ALLUSERSPROFILE") + @"\Start Menu");
            }
            FileInfo[] links = di.GetFiles("*.lnk", SearchOption.AllDirectories);

            foreach (FileInfo f in links)
            {
                tsmlist.Add(new StartMenuItem(f.FullName, Path.GetFileNameWithoutExtension(f.Name)));
            }

            links = di.GetFiles("*.url", SearchOption.AllDirectories);
            foreach (FileInfo f in links)
            {
                tsmlist.Add(new StartMenuItem(f.FullName, Path.GetFileNameWithoutExtension(f.Name)));
            }

            di = new DirectoryInfo(System.Environment.GetFolderPath(Environment.SpecialFolder.StartMenu));
            links = di.GetFiles("*.lnk", SearchOption.AllDirectories);
            foreach (FileInfo f in links)
            {
                tsmlist.Add(new StartMenuItem(f.FullName, Path.GetFileNameWithoutExtension(f.Name)));
            }
            links = di.GetFiles("*.url", SearchOption.AllDirectories);
            foreach (FileInfo f in links)
            {
                tsmlist.Add(new StartMenuItem(f.FullName, Path.GetFileNameWithoutExtension(f.Name)));
            }
            tsmlist.Sort();
            smlist = tsmlist;
        }
        /// <summary>
        /// Rebuilds the DOMercury Index on a separate thread, then switches the index when complete
        /// </summary>
        public static void RebuildIndex()
        {
            if (!PauseRefresh)
            {
                System.ComponentModel.BackgroundWorker bw = new System.ComponentModel.BackgroundWorker();
                bw.DoWork += new System.ComponentModel.DoWorkEventHandler(bw_DoWork);
                bw.RunWorkerAsync();
            }
        }

        static void bw_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
        {
            pRebuildIndex();
        }

        #endregion
        #region History Management Functions
        private static void ReadItems(ref List<TextItem> items)
        {
            XmlTextReader r = new XmlTextReader(new FileStream(Path.GetDirectoryName(Application.ExecutablePath) + "\\AutoComplete.xml", FileMode.Open, FileAccess.Read));
            while ((!r.EOF) && r.Name.Trim().CompareTo("Items") != 0)
            {
                r.Read();
            }
            r.Read();
            //r.Read();
            //r.Read();
            while ((!r.EOF) && r.Name.Trim().CompareTo("Items") != 0)
            {
                if (r.NodeType == XmlNodeType.EndElement)
                {
                    r.Read();
                }
                else
                {
                    switch (r.Name.Trim())
                    {
                        case "Item":
                            items.Add(new TextItem(ReadItem(r)));
                            r.Skip();
                            break;
                        case "Param":
                            Params.Add(ReadItem(r));
                            ParamsTexts.Add(new TextItem(ReadItem(r)));
                            ParamsTextsIndex.Add(new IItemContainer(new TextItem(ReadItem(r))));
                            r.Skip();
                            break;
                        case "":
                            r.Read(); break;
                        default:
                            r.Skip(); break;
                    }
                }
            }
            r.Close();
        }

        private static string ReadItem(XmlTextReader r)
        {
            return r.GetAttribute("Value");
        }

        public static void AddToItemHistory(TextItem item)
        {
            if (item.Text.ToLower() != "current window")
            {
                if (!(item.Text.Length < 2))
                {
                    if (!history.Contains(item))
                    {
                        if (!((item.Text[1] == ':') && (item.Text.Length < 4)))
                        {
                            history.Add(item);
                            Save();
                        }
                    }
                }
            }
        }

        public static void AddToParamHistory(string param)
        {
            
                if (!(param.Length < 2))
                {
                    if (!Params.Contains(param))
                    {
                        if (!((param[1] == ':') && (param.Length < 4)))
                        {
                            Params.Add(param);
                            ParamsTexts.Add(new TextItem(param));
                            ParamsTextsIndex.Add(new IItemContainer(new TextItem(param)));
                            Save();
                        }
                    }
                }
            
        }

        private static void Save()
        {
            XmlTextWriter w = new XmlTextWriter(new StreamWriter(Path.GetDirectoryName(Application.ExecutablePath) + "\\AutoComplete.xml", false, Encoding.UTF8));
            w.WriteStartElement("Items");
            foreach (TextItem s in history)
            {
                w.WriteStartElement("Item");
                w.WriteAttributeString("Value", s.Text);
                w.WriteEndElement();
            }
            foreach (string s in Params)
            {
                w.WriteStartElement("Param");
                w.WriteAttributeString("Value", s);
                w.WriteEndElement();
            }
            w.WriteEndElement();
            w.Flush();
            w.Close();
        }
#endregion
        #region Public Data Request Functions
        /// <summary>
        /// Returns results from the current index based on the text parameter
        /// </summary>
        /// <param name="text">text string to match against the display name of the indexed items.</param>
        /// <returns>A list of matching result IItems</returns>
        public static List<IItem> GetItems(string text)
        {
            //Should use delegates for matching instead, oh well
            return GetItems(text, SMARTSEARCH, true);
        }

        /// <summary>
        /// Returns results from the current param index based on the text parameter
        /// </summary>
        /// <param name="text">text string to match against the display name of the indexed items.</param>
        /// <returns>A list of matching result IItems</returns>
        public static List<IItem> GetParamItems(string text)
        {
            //Should use delegates for matching instead, oh well
            return GetItems(text, SMARTSEARCH, false);
        }

        /// <summary>
        /// Used by the Priority Manager to get items to display on the Manage Priorities Form
        /// </summary>
        /// <param name="text"></param>
        /// <param name="Number"></param>
        /// <returns></returns>
        public static List<IItemContainer> PGetItems(string text, decimal Number)
        {
            decimal i = 0;
            StringBuilder rs = new StringBuilder(".*");
            foreach (char c in text.ToLower().ToCharArray())
            {
                if ((c == '.') ||
                    (c == '[') ||
                    (c == ']') ||
                    (c == '(') ||
                    (c == ')') ||
                    (c == '^') ||
                    (c == '$') ||
                    (c == '*') ||
                    (c == '?') ||
                    (c == '+')

                    )
                {
                    rs.Append("\\");
                }
                rs.Append(c.ToString());
                rs.Append(".*");
            }
            Regex txt = new Regex(rs.ToString());
            List<IItemContainer> results = new List<IItemContainer>();
            if (text != "")
            {
                
                foreach (IItemContainer item in index)
                {
                    if (i < Number)
                    {
                        if (txt.IsMatch(item.DisplayName.ToLower()))
                        {
                            results.Add(item);
                            i = i + 1m;
                        }
                    }
                }                

            }
            return results;
        }
        
        /// <summary>
        /// Return Results from the Main DOMercury index, regardless of the current index.  Uses Smart Search
        /// </summary>
        /// <param name="text">text string to match against the display name of the indexed items.</param>
        /// <param name="Number">the maximum number of results to return</param>
        /// <returns>L list of matching result IItems</returns>
        public static List<IItem> MainIndexGetItems(string text, int Number)
        {
            int i = 0;
            StringBuilder rs = new StringBuilder(".*");
            foreach (char c in text.ToLower().ToCharArray())
            {
                if ((c == '.') ||
                    (c == '[') ||
                    (c == ']') ||
                    (c == '(') ||
                    (c == ')') ||
                    (c == '^') ||
                    (c == '$') ||
                    (c == '*') ||
                    (c == '?') ||
                    (c == '+')

                    )
                {
                    rs.Append("\\");
                }
                rs.Append(c.ToString());
                rs.Append(".*");
            }
            Regex txt = new Regex(rs.ToString());
            List<IItem> results = new List<IItem>();
            if (text != "")
            {

                foreach (IItemContainer item in index)
                {
                    if (i < Number)
                    {
                        if (txt.IsMatch(item.DisplayName.ToLower()))
                        {
                            results.Add(item.InnerItem);
                            i = i + 1;
                        }
                    }
                }

            }
            return results;
        }

        #region Get Items Modifiers

        //Returns results based on the "SmartSearch" Regular Expression searching method
        // OR the "QuickSearch" substring match method (faster)
        private static List<IItem> GetItems(string text, bool useSmartSearch, bool IItems)
        {
            List<IItemContainer> m_currentIndex, m_index;
            bool m_SearchOn;
            bool needToSort = false;
            if (IItems)
            {
                m_currentIndex = currentIndex;
                m_index = index;
                m_SearchOn = SearchOn;
            }
            else
            {
                m_index = pindex;
                m_currentIndex = pcurrentIndex;
                m_SearchOn = pSearchOn;
            }

            Regex txt = null;
            List<IItemContainer> results = new List<IItemContainer>();

            if (useSmartSearch)
            {
                //generate a regular expression based on the input text
                StringBuilder rs = new StringBuilder(".*");
                foreach (char c in text.ToLower().ToCharArray())
                {
                    if ((c == '.') ||
                        (c == '[') ||
                        (c == ']') ||
                        (c == '(') ||
                        (c == ')') ||
                        (c == '^') ||
                        (c == '$') ||
                        (c == '*') ||
                        (c == '?') ||
                        (c == '+')

                        )
                    {
                        rs.Append("\\");
                    }
                    rs.Append(c.ToString());
                    rs.Append(".*");
                }
                txt = new Regex(rs.ToString());
            }

            //if the search text is not blank
            if (text != "")
            {
                //return alias matches if the current index is the main index
                if (m_currentIndex == index)
                {
                    m_SearchOn = true;
                    foreach (IItemContainer a in AliasManager.AliasContainers)
                    {
                        //if (text == a.DisplayName.ToLower())
                        //{
                        //    results.Insert(0, a);
                        //}
                        //else 
                            if ((useSmartSearch && txt.IsMatch(a.DisplayName.ToLower())) ||
                            (!useSmartSearch && a.DisplayName.ToLower().Contains(text)))
                        {
                            results.Add(a);
                        }
                    }
                

                    //If the current index is the main DOMercury index, return all system window and file path matches
                    //lock (SystemWindowManager.SystemWindows)
                    //{
                    //    lock (results)
                    //    {   
                            foreach (IItemContainer w in SystemWindowManager.SystemWindows)
                            {
                                if ((useSmartSearch && txt.IsMatch(w.DisplayName)) || (!useSmartSearch && w.DisplayName.Contains(text)))
                                {
                                     needToSort = true;
                                     try
                                     {
                                         results.Add(w);
                                     }
                                     catch
                                     {
                                         //No SystemWindow For You!
                                     }
                                }
                            }
                    //    }
                    //}
                }
                
            
                //if main index, then use cached results where available, and keep cache otherwise:
                if (m_currentIndex == m_index)
                {
                    
                    if (cachedIndexResults.ContainsKey(text) && SMARTSEARCH == cachedIndexesUseSmartSearch)
                    {
                        //results.AddRange(UnpackContainerList(cachedIndexResults[text]));
                        results.AddRange(cachedIndexResults[text]);
                    }
                    else
                    {
                        if (useSmartSearch != cachedIndexesUseSmartSearch)
                        {
                            cachedIndexResults.Clear();
                            cachedIndexResultIndices.Clear();
                            cachedIndexesUseSmartSearch = useSmartSearch;
                        }

                        bool breakout = false;
                        int i = 0;

                        List<IItemContainer> cacheList = new List<IItemContainer>();

                        string bestCacheKey = "";

                        //find the best appropriate / useful key to help.
                        foreach (string candidateKey in cachedIndexResults.Keys)
                        {
                            if (text.StartsWith(candidateKey))
                                if (candidateKey.Length > bestCacheKey.Length)
                                    bestCacheKey = candidateKey;
                        }

                        //if found, do prelim search and fast-forward i 
                        if (bestCacheKey.Length > 0)
                        {
                            List<IItemContainer> previousCacheList = cachedIndexResults[bestCacheKey];

                            for (; i < previousCacheList.Count && !breakout; i++)
                                GetSingleItem(ref cacheList, previousCacheList[i], text, txt, ref breakout, useSmartSearch);

                            i = cachedIndexResultIndices[bestCacheKey];
                            breakout = false; //reset for reuse below
                        }

                        for (; i < currentIndex.Count && !breakout; i++)
                            GetSingleItem(ref cacheList, m_currentIndex[i], text, txt, ref breakout, useSmartSearch);

                        //results.AddRange(UnpackContainerList(cacheList));
                        results.AddRange(cacheList);
                        if (!cachedIndexResults.ContainsKey(text))
                        {
                            cachedIndexResults.Add(text, cacheList);
                            cachedIndexResultIndices.Add(text, i);
                        }

                    }
                }
                else
                {
                    if (m_SearchOn) //if this is a subindex with search, do regular search (no caching)
                    {
                        List<IItemContainer> subindexMatchList = new List<IItemContainer>();
                        bool breakout = false;

                        for (int i = 0; i < m_currentIndex.Count && !breakout; i++)
                        {
                            GetSingleItem(ref subindexMatchList, m_currentIndex[i], text, txt, ref breakout, useSmartSearch);
                        }

                        //results.AddRange(UnpackContainerList(subindexMatchList));
                        results.AddRange(subindexMatchList);
                    }
                    else //if subindex without search, just add everything.
                    {
                        for (int i = 0; i < m_currentIndex.Count; i++)
                        {
                            try
                            {
                                //results.Add((IItemContainer)(m_currentIndex[i]).InnerItem);
                                results.Add(m_currentIndex[i]);
                            }
                            catch { }
                        }
                    }
                }
                
            }

            //if text == "" and not the main DOMercury index, return all items
            else if (m_currentIndex != index)
            {
                List<IItem> tmp = new List<IItem>();
                foreach (IItemContainer ic in m_currentIndex)
                {
                    tmp.Add(ic.InnerItem);
                }
                return tmp;
            }

            //Properly mix realtime items
            if (needToSort)
            {
                results.Sort();
            }

            List<IItem> ReturnResults = new List<IItem>();
            

            //Create PathItem if exists
            if (m_currentIndex == m_index)
            {
                
                if (Directory.Exists(PathFinder.FindPath(text)))
                {
                    List<PathItem> tmp = new List<PathItem>();
                    if (text.EndsWith(":"))
                    {
                        text = text + "\\";
                    }
                    tmp.Add(new PathItem(PathFinder.FindPath(text), text));
                    ReturnResults.Add((IItem)(new ItemsList(tmp)));
                }
                else if (File.Exists(text))
                {
                    List<PathItem> tmp = new List<PathItem>();

                    tmp.Add(new PathItem(text, Path.GetFileNameWithoutExtension(text)));
                    ReturnResults.Add((IItem)(new ItemsList(tmp)));
                }
                
            }
            
            ReturnResults.AddRange(UnpackContainerList(results));

            if (text != "")
            {
                ReturnResults.Add(new TextItem(text));
            }
            return ReturnResults;
        }

        


        private static void GetSingleItem(ref List<IItemContainer> buildList, IItemContainer container, string text, Regex txt, ref bool breakout, bool useSmartSearch)
        {
            try
            {
                if (container.DisplayName.ToLower() == text)
                {
                    buildList.Insert(0, container);
                }
                else if ((useSmartSearch && txt.IsMatch(container.DisplayName.ToLower())) ||
                    (!useSmartSearch && container.DisplayName.ToLower().Contains(text)))
                {
                    buildList.Add(container);
                }
                if (buildList.Count > MATCHLIM)
                {
                    breakout = true;
                }
            }
            catch
            {
            }
        }

        private static List<IItem> UnpackContainerList(List<IItemContainer> list)
        {
            List<IItem> theseItems = new List<IItem>();
            foreach (IItemContainer container in list)
                theseItems.Add(container.InnerItem);
            return theseItems;
        }

        #endregion

        /// <summary>
        /// Return a combobox autocompletesource based on the current item type
        /// </summary>
        /// <param name="type">The ItemType of the current item</param>
        /// <returns></returns>
        public static System.Windows.Forms.AutoCompleteSource ChangeItemAutoCompleteSource(string type)
        {
            switch (type)
            {
                case "folder":
                    return AutoCompleteSource.FileSystemDirectories;
                    
                    break;
                default:
                    return AutoCompleteSource.None;
                    
                    break;
            }
        }

        public static void ChangeParametersAutoCompleteSource(out List<IItem> Parameters, out System.Windows.Forms.AutoCompleteMode AutoCompleteMode, out System.Windows.Forms.AutoCompleteSource AutoCompleteSource, out AutoCompleteStringCollection CustomSource, string pType, string iType)
        {

            AutoCompleteMode = AutoCompleteMode.SuggestAppend;
            AutoCompleteSource = AutoCompleteSource.CustomSource;
            CustomSource = Params;
            Parameters = null;
            switch (pType)
            {
                case "Path":
                    Parameters = GetDriveIItems();
                    AutoCompleteSource = AutoCompleteSource.FileSystemDirectories;
                    AutoCompleteMode = AutoCompleteMode.SuggestAppend;
                    break;
                case "PathFiles":
                    Parameters = GetDriveIItems();
                    AutoCompleteSource = AutoCompleteSource.FileSystem;
                    AutoCompleteMode = AutoCompleteMode.SuggestAppend;
                    break;
                case "URL":
                    Parameters = ParamsTexts;
                    AutoCompleteSource = AutoCompleteSource.AllUrl;
                    AutoCompleteMode = AutoCompleteMode.SuggestAppend;
                    break;
                case "OpenWith":
                    AutoCompleteSource = AutoCompleteSource.None;
                    Parameters = RegistryManager.GetOpenWithExes(iType);
                    AutoCompleteMode = AutoCompleteMode.None;
                    break;
                case "CommandLine":
                    Parameters = ParamsTexts;
                    AutoCompleteMode = AutoCompleteMode.SuggestAppend;
                    AutoCompleteSource = AutoCompleteSource.CustomSource;
                    CustomSource = Params;
                    break;
                case "Unused":
                    Parameters = ParamsTexts;
                    //cbParams.DropDownStyle = ComboBoxStyle.DropDown;
                    AutoCompleteMode = AutoCompleteMode.SuggestAppend;
                    AutoCompleteSource = AutoCompleteSource.CustomSource;
                    CustomSource = Params;
                    break;
                default:
                    bool found = false;
                    foreach (IParametersProvider p in PluginManager.ParamProviders)
                    {
                        if (pType == p.ParameterType)
                        {
                            AutoCompleteSource = AutoCompleteSource.None;
                            AutoCompleteMode = AutoCompleteMode.None;
                            Parameters = p.ParameterDataSource;
                            found = true;
                            break;
                        }
                    }
                    if (!found)
                    {
                        Parameters = ParamsTexts;
                        AutoCompleteMode = AutoCompleteMode.SuggestAppend ;
                        AutoCompleteSource = AutoCompleteSource.CustomSource;
                        CustomSource = Params;
                    }
                    break;
            }

            //Last thing, set the main parameters index to the one sent
            if (ParamsTexts != Parameters)
            {
                pindex = CreateParamsIndex(Parameters);
            }
            else
            {
                pindex = ParamsTextsIndex;
            }
        }

        private static List<IItemContainer> CreateParamsIndex(List<IItem> currentParams)
        {
            List<IItemContainer> paramsIndex = new List<IItemContainer>();
            foreach (IItem item in currentParams)
            {
                paramsIndex.Add(new IItemContainer(item));
            }
            return paramsIndex;
        }

        private static List<IItem> GetDriveIItems()
        {
            List<IItem> items = new List<IItem>(); ;
            // Store the list of drives into an array of string
            string[] DriveList = Environment.GetLogicalDrives();
            // Loop through the array
            foreach (string i in DriveList)
            {
                PathItem path = new PathItem(i, i);
                List<PathItem> paths = new List<PathItem>();
                paths.Add(path);
                ItemsList driveitem = new ItemsList(paths);
                items.Add(driveitem);
            }
            return items;
        }


        /// <summary>
        /// Sets the Current Index to the main DOMercury Index
        /// </summary>
        public static void ReturnMainIndex()
        {
            if (currentIndex != index)
            {
                currentIndex = index;
                SearchOn = true;
                istack.PopAll();
                itstack.PopAll();
                sstack.PopAll();
            }
             
        }

        /// <summary>
        /// Sets the Current Param Index to the main Param Index
        /// </summary>
        public static void ReturnParamMainIndex()
        {
            if (pcurrentIndex != pindex)
            {
                pcurrentIndex = pindex;
                pSearchOn = true;
                pistack.PopAll();
                pitstack.PopAll();
                psstack.PopAll();
            }

        }

        /// <summary>
        /// Sets the DOMercury current index to a sub index based on an IItem, keyword text, and the current selected action
        /// </summary>
        /// <param name="item"></param>
        /// <param name="itemtext"></param>
        /// <param name="action"></param>
        /// <returns></returns>
        public static bool SetSubIndex(IItem item, string itemtext, IAction action, IItem param)
        {                
            List<IItemContainer> tsindex = new List<IItemContainer>();
            bool es = true;
            foreach (ISubIndex i in PluginManager.SubIndexes)
            {
                //Find the proper sub index to use based on itemtype
                if (item.ItemType.ToLower() == i.ParentItemType.ToLower())
                {
                    //add the sub index items
                    List<IItemContainer> tmp = new List<IItemContainer>();
                    foreach (IItem it in i.GetSubIndex(item, action))
                    {
                        tmp.Add(new IItemContainer(it));
                    }
                    tsindex.AddRange(tmp);
                    es = i.EnableSearch;
                }
            }

            //push the index to the stack
            if (tsindex.Count > 0)
            {
                SearchOn = es;
                currentIndex = tsindex;
                istack.Push(tsindex, es);
                itstack.Push(item);
                sstack.Push(itemtext);
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Sets the DOMercury current param index to a sub index based on an IItem, keyword text, and the current selected action
        /// </summary>
        /// <param name="item"></param>
        /// <param name="itemtext"></param>
        /// <param name="action"></param>
        /// <returns></returns>
        public static bool SetParamSubIndex(IItem param, string itemtext, IAction action)
        {
            List<IItemContainer> tsindex = new List<IItemContainer>();
            bool es = true;
            foreach (ISubIndex i in PluginManager.SubIndexes)
            {
                //Find the proper sub index to use based on itemtype
                if (param.ItemType.ToLower() == i.ParentItemType.ToLower())
                {
                    //add the sub index items
                    List<IItemContainer> tmp = new List<IItemContainer>();
                    foreach (IItem it in i.GetSubIndex(param, action))
                    {
                        tmp.Add(new IItemContainer(it));
                    }
                    tsindex.AddRange(tmp);
                    es = i.EnableSearch;
                }
            }

            //push the index to the stack
            if (tsindex.Count > 0)
            {
                pSearchOn = es;
                pcurrentIndex = tsindex;
                pistack.Push(tsindex, es);
                pitstack.Push(param);
                psstack.Push(itemtext);
                return true;
            }
            else
            {
                return false;
            }
        }


        //pop the current index to the previous index
        public static void PreviousIndex()
        {
            if (currentIndex != index)
            {
                istack.Pop();
                itstack.Pop();
                sstack.Pop();
                currentIndex = istack.Top;
                SearchOn = istack.TopSearch;
            }
        }

        ///pop the current param index to the previous index
        public static void PreviousParamIndex()
        {
            if (pcurrentIndex != pindex)
            {
                pistack.Pop();
                pitstack.Pop();
                psstack.Pop();
                pcurrentIndex = pistack.Top;
                pSearchOn = pistack.TopSearch;
            }
        }

        /// <summary>
        /// returns the current parent item
        /// </summary>
        public static IItem ParentItem
        {
            get
            {
                return itstack.Top;
            }
        }
        
        /// <summary>
        /// returns the parent search text
        /// </summary>
        public static string ParentText
        {
            get
            {
                return sstack.Top;
            }
        }

        /// <summary>
        /// returns the current parent item
        /// </summary>
        public static IItem ParentParamItem
        {
            get
            {
                return pitstack.Top;
            }
        }

        /// <summary>
        /// returns the parent search text
        /// </summary>
        public static string ParentParamText
        {
            get
            {
                return psstack.Top;
            }
        }

        #endregion
        #region DiagnosticFunctions
        public static void ItemTypeCount()
        {
            Dictionary<string, ReferenceInt> types = new Dictionary<string, ReferenceInt>();
            PauseRefresh = true;
            ReferenceInt value;
            bool exists;
            foreach (IItemContainer i in index)
            {
                exists = types.TryGetValue(i.ItemType, out value);
                if (exists)
                {
                    value.Int++;
                    
                }
                else
                {
                    types.Add(i.ItemType, new ReferenceInt(1));
                }
            }
            PauseRefresh = false;
        }
        #endregion

        /// <summary>
        /// Provides a TextItem based on the itemtext
        /// </summary>
        /// <param name="itemtext">the text to be assigned to the textitem</param>
        /// <returns></returns>
        public static List<IItem> ProvideTextItem(string itemtext)
        {
            List<IItem> txtitem = new List<IItem>();
            txtitem.Add(new TextItem(itemtext));
            return txtitem;
        }
    }

    /// <summary>
    /// A stack to manage DOMercury's sub indexes
    /// </summary>
    public class IndexStack
    {
        private LinkedList<bool> es;
        private LinkedList<List<IItemContainer>> stack;
        public IndexStack(List<IItemContainer> baseIndex)
        {
            stack = new LinkedList<List<IItemContainer>>();
            stack.AddLast(baseIndex);
            es = new LinkedList<bool>();
            es.AddLast(true);
        }

        /// <summary>
        /// Returns the top of the stack
        /// </summary>
        public List<IItemContainer> Top
        {
            get
            {
                return stack.Last.Value;
            }
        }


        /// <summary>
        /// 
        /// </summary>
        public bool TopSearch
        {
            get
            {
                return es.Last.Value;
            }
        }

        public List<IItemContainer> Bottom
        {
            get
            {
                return stack.First.Value;
            }
        }

        public void Push(List<IItemContainer> index, bool SearchOn)
        {
            stack.AddLast(index);
            es.AddLast(SearchOn);
        }

        public void Pop()
        {
            if (stack.Count > 1)
            {
                stack.RemoveLast();
                es.RemoveLast();
            }
        }

        public void PopAll()
        {
            while (stack.Count > 1)
            {
                stack.RemoveLast();
                es.RemoveLast();
            }
        }

        //Allows for switching out the base stack.  Useful if the main index is reindexed while the user is on a sub index.
        public void ResetBase(List<IItemContainer> baseIndex)
        {
            stack.RemoveFirst();
            stack.AddFirst(baseIndex);
        }
    }
    public class ItemStack
    {
        private LinkedList<IItem> stack;
        public ItemStack()
        {
            stack = new LinkedList<IItem>();
            //stack.AddLast(baseIndex);
        }

        public IItem Top
        {
            get
            {
                try
                {
                    return stack.Last.Value;
                }
                catch
                {
                    return null;
                }
            }
        }

        public IItem Bottom
        {
            get
            {
                return stack.First.Value;
            }
        }

        public void Push(IItem item)
        {
            stack.AddLast(item);
        }

        public void Pop()
        {
            if (stack.Count > 0)
            {
                stack.RemoveLast();
            }
        }

        public void PopAll()
        {
            while (stack.Count > 0)
            {
                stack.RemoveLast();
            }
        }
    }
    public class StringStack
    {
        private LinkedList<string> stack;
        public StringStack()
        {
            stack = new LinkedList<string>();
            
        }

        public string Top
        {
            get
            {
                try
                {
                    return stack.Last.Value;
                }
                catch
                {
                    return "";
                }
            }
        }

        public string Bottom
        {
            get
            {
                return stack.First.Value;
            }
        }

        public void Push(string item)
        {
            stack.AddLast(item);
        }

        public void Pop()
        {
            if (stack.Count > 0)
            {
                stack.RemoveLast();
            }
        }

        public void PopAll()
        {
            while (stack.Count > 0)
            {
                stack.RemoveLast();
            }
        }
    }

    public class PriorityRep
    {
        public string DisplayName;
        public string DisplayDesc;
        public int Priority = 0;
        public PriorityRep(string DN, string DD, int Prty)
        {
            DisplayDesc = DD;
            DisplayName = DN;
            Priority = Prty;
        }
    }
    public class ReferenceInt
    {
        public ReferenceInt(int num)
        {
            Int = num;
        }
        public int Int;
    }

    //Holds an IItem and its priority
    public class IItemContainer : IItem
    {
        public IItem item;
        public int Priority;
        public IItemContainer(IItem Item)
        {
            item = Item;
        }

        public IItemContainer(IItem Item, int Prty)
        {
            item = Item;
            Priority = Prty;
        }

        public IItem InnerItem
        {
            get
            {
                return item;
            }
        }
        #region IItem Members

        public string DisplayName
        {
            get { return item.DisplayName; }
        }

        public string ItemType
        {
            get { return item.ItemType; }
        }

        public string DisplayDesc
        {
            get { return item.DisplayDesc; }
        }

        public int CompareTo(object obj)
        {
            int compare = ((IItemContainer)(obj)).Priority.CompareTo(Priority);
            if (compare != 0)
            {
                return compare;
            }
            else
            {
                return DisplayName.CompareTo(((IItemContainer)(obj)).DisplayName);
            }
        }

        #endregion
    }

}
